home *** CD-ROM | disk | FTP | other *** search
- /* hAWK_FilesandClip.c : finally, some file manipulation routines for hAWK.
- Where a file or directory name is needed, they must be full pathnames
- in order to give predictable results.
- copy : copy a file from one location to another, optionally changing name.
- list : given file or directory full pathname, returns array of full pathnames
- for all TEXT files in the directory. Note subdirectories are also excluded.
- nested : given file full pathname, returns array of full pathnames for
- directories at the same level ("sibling folders"); given directory
- name, returns array of subdirectories. In other words, the same as
- list but for folders rather than files.
- remove : deletes a file
- rename : renames a file (without changing location). Note the old name
- must be a full pathname, but the new name can be either a full pathname
- or just a file name proper (no colons).
- exists : returns 1 if file exists, 0 if not
- fdate : returns date/time of last modification, format
- yr:mo:day:hr:min:sec where yr is 4 digits, and the
- rest are 2 (eg always 01 rather than just 1)
- fsize : returns size in bytes of file's data fork
- getclip : returns current calling app's clipboard text, as a string
- (optional argument to specify max bytes of text retrieved)
- putclip : puts a string on the calling app's private clipboard
- beep : does a SysBeep(); if the duration you pass is <= 0, the
- menu bar will flash instead of a beep
- */
- #include "AWK.H"
- #include <Files.h>
- extern void assoc_clear(NODE *symbol); /* ARRAY.C */
-
-
- /* Functions defined in this file */
- short b_get_two(NODE *tree, NODE **res1, NODE **res2);
- short c_get_two(NODE *tree, NODE **res1, NODE **res2);
- NODE *do_copy(NODE *tree);
- Boolean copy_file(Ptr oldNameP, short oldNmLen, Ptr newNameP, short newNmLen);
- OSErr CreateDirectories(StringPtr newName);
- NODE *do_list(NODE *tree);
- NODE *do_nested(NODE *tree);
- short list_files_or_dirs(char *theName, short nmlen, NODE *ind, Boolean dirs);
- NODE *do_rename(NODE *tree);
- Boolean _rename(Ptr oldNameP, short oldNmLen, Ptr newNameP, short newNmLen);
- NODE *do_remove(NODE *tree);
- Boolean _remove(char *name, short nmLen);
- NODE *do_exists(NODE *tree);
- Boolean _exists(char *name, short nmLen);
- NODE *do_fdate(NODE *tree);
- NODE *_fdate(char *name, short nmLen);
- NODE *do_fsize(NODE *tree);
- long _fsize(char *name, short nmLen);
- NODE *do_getclip(NODE *tree);
- NODE *_getclip(long maxBytes);
- NODE *do_putclip(NODE *tree);
- short _putclip(char *str);
- NODE *do_beep(NODE *tree);
-
- short b_get_two(NODE *tree, NODE **res1, NODE **res2)
- {
- if (!tree)
- return 0;
- *res1 = tree_eval(tree->lnode);
- if (!tree->rnode)
- return 1;
- tree = tree->rnode;
- *res2 = tree->lnode;
- return 2;
- }
-
-
- short c_get_two(NODE *tree, NODE **res1, NODE **res2)
- {
- if (!tree)
- return 0;
- *res1 = tree_eval(tree->lnode);
- if (!tree->rnode)
- return 1;
- tree = tree->rnode;
- *res2 = tree_eval(tree->lnode);
- return 2;
- }
- NODE *do_copy(NODE *tree)
- {
- NODE *t1, *t2; /* string, string */
- NODE *res;
- char *from, *to;
- short num_args;
-
- if ((num_args = c_get_two(tree, &t1, &t2)) < 2)
- fatal("copy requires at least two arguments");
- from = force_string(t1)->stptr;
- to = force_string(t2)->stptr;
- res = tmp_number((AWKNUM)
- copy_file(from, t1->stlen+1, to, t2->stlen+1));
- free_temp(t1);
- free_temp(t2);
- return res;
- }
-
- /* Open & copy data forks, and resource forks. Use as big a buffer as you can get.
- Requires a non-empty data fork in original file.
- Preserves type and creator.*/
- Boolean copy_file(Ptr oldNameP, short oldNmLen, Ptr newNameP, short newNmLen)
- {
- IOParam pb, pbOut;
- FileParam fpb;
- long eof, count, bufSize, minBuf, numLeft, theType, theCreator;
- Handle hData;
- char oldName[256], newName[256];
- OSErr IOResult;
- short i;
- #define FileError(x)
-
- BlockMove(oldNameP, oldName, oldNmLen);
- CtoPstr(oldName);
- fpb.ioNamePtr = (StringPtr) oldName;
- fpb.ioVRefNum = 0;
- fpb.ioFVersNum = 0;
- fpb.ioFDirIndex = 0;
- if (PBGetFInfoSync((ParmBlkPtr)&fpb) != noErr)
- return(FALSE);
- theType = fpb.ioFlFndrInfo.fdType;
- theCreator = fpb.ioFlFndrInfo.fdCreator;
-
- BlockMove(oldNameP, oldName, oldNmLen);
- CtoPstr(oldName);
- BlockMove(newNameP, newName, newNmLen);
- CtoPstr(newName);
-
- for (i = 0; i <= 1; ++i) /* 0 == data, 1 == resource fork */
- {
- numLeft = 0;
- hData = NULL;
- /* Set up the param block to read in the old fork. */
- pb.ioCompletion = NULL;
- pb.ioNamePtr = (StringPtr)oldName;
- pb.ioVRefNum = 0;
- pb.ioVersNum = 0;
- pb.ioPermssn = fsRdPerm;
- pb.ioMisc = NULL;
- if (i == 0)
- {
- if (PBOpen((ParmBlkPtr)&pb, FALSE))
- { FileError(pb.ioResult); return(FALSE); }
- }
- else
- {
- if (PBOpenRF((ParmBlkPtr)&pb, FALSE)) /* missing resource fork is not an error */
- return(TRUE);
- }
- GetEOF(pb.ioRefNum, &eof);
- if (!eof && i) /* empty resource fork - OK */
- {
- PBClose((ParmBlkPtr)&pb, FALSE);
- return(TRUE);
- }
- bufSize = eof;
- minBuf = 1024L;
- if (minBuf > eof)
- minBuf = 128L;
- hData = NewHandle(bufSize);
- while (MemError() != noErr)
- {
- bufSize /= 2L;
- if (bufSize < minBuf)
- { /*DoMemoryAlert(fileErrMsg, minBuf);*/ goto Abort; }
- hData = NewHandle(bufSize);
- }
- /* delete, create and open new file, allocating space on disk */
- if (i == 0)
- {
- FSDelete((StringPtr)newName, 0); // ignore any error
- if (IOResult = Create((StringPtr)newName, 0, theCreator, theType))
- {
- if (IOResult = CreateDirectories((StringPtr)newName)) // calls DirCreate repeatedly
- { FileError(IOResult); goto Abort; }
- // Try again with folders newly in place.
- if (IOResult = Create((StringPtr)newName, 0, theCreator, theType))
- { FileError(IOResult); goto Abort; }
- }
- }
- /* set up pbOut for the file with write permission and open the file */
- pbOut.ioCompletion = NULL;
- pbOut.ioNamePtr = (StringPtr)newName;
- pbOut.ioVRefNum = 0;
- pbOut.ioVersNum = 0;
- pbOut.ioPermssn = fsWrPerm;
- pbOut.ioMisc = NULL;
- if (i == 0)
- {
- if (PBOpen((ParmBlkPtr)&pbOut, FALSE))
- { FileError(pbOut.ioResult); goto Abort; }
- }
- else
- {
- if (PBOpenRF((ParmBlkPtr)&pbOut, FALSE))
- { FileError(pbOut.ioResult); goto Abort; }
- }
- pbOut.ioMisc = (Ptr)eof;
- if (PBSetEOF((ParmBlkPtr)&pbOut, FALSE))
- { FileError(pbOut.ioResult); goto Abort; }
- /* set file marks to beginning of file */
- pb.ioMisc = NULL;
- pb.ioPosMode = fsFromStart;
- pb.ioPosOffset = 0L;
- if (PBSetFPos((ParmBlkPtr)&pb, FALSE))
- { FileError(pb.ioResult); goto Abort; }
- pbOut.ioMisc = NULL;
- pbOut.ioPosMode = fsFromStart;
- pbOut.ioPosOffset = 0L;
- if (PBSetFPos((ParmBlkPtr)&pbOut, FALSE))
- { FileError(pbOut.ioResult); goto Abort; }
- /* r/w until done */
- pb.ioBuffer = pbOut.ioBuffer = *hData; /* note no need to lock it */
- numLeft = eof;
- if (numLeft >= bufSize)
- pb.ioReqCount = pbOut.ioReqCount = bufSize;
- else
- pb.ioReqCount = pbOut.ioReqCount = numLeft;
- while (numLeft > 0)
- {
- if (numLeft < bufSize)
- pb.ioReqCount = pbOut.ioReqCount = numLeft;
- if (PBRead((ParmBlkPtr)&pb, FALSE))
- { FileError(pb.ioResult); goto Abort; }
- if (PBWrite((ParmBlkPtr)&pbOut, FALSE))
- { FileError(pbOut.ioResult); goto Abort; }
- if (pb.ioActCount != pbOut.ioActCount)
- {
- /* if not for hAWK, notify user... */
- goto Abort;
- }
- numLeft -= pb.ioActCount;
- }
-
- if (PBClose((ParmBlkPtr)&pb, FALSE))
- { FileError(pb.ioResult); goto Abort; }
- if (PBClose((ParmBlkPtr)&pbOut, FALSE))
- { FileError(pbOut.ioResult); goto Abort; }
- DisposHandle(hData);
- }
- return(TRUE);
-
- Abort:
- if (hData)
- DisposHandle(hData);
- FSClose (pb.ioRefNum);
- FSClose (pbOut.ioRefNum);
- FSDelete((StringPtr)newName, 0);
- return(FALSE);
- #undef FileError
- }
-
- // Attempt to create directories along the newName proposed path.
- // We start at the root, for want of a better place.
- // A full path name is more or less assumed.
- OSErr CreateDirectories(StringPtr newName)
- {
- OSErr io;
- long parentDirID, createdDirID;
- short reallyTrueLen, trueLen, currentLen;
-
- if (newName[1] == ':') // not a full path name
- return bdNamErr;
- // trim file name from path
- reallyTrueLen = trueLen = newName[0];
- while (newName[trueLen] != ':' && trueLen > 0)
- --trueLen;
- if (trueLen <= 4) // minimum is a:b:c
- return bdNamErr;
- currentLen = 2;
- // skip to a colon
- while (newName[currentLen] != ':' && currentLen < trueLen)
- ++currentLen;
- if (newName[currentLen] != ':' || currentLen >= trueLen)
- return bdNamErr;
- // and skip to another colon
- ++currentLen;
- while (currentLen <= trueLen && newName[currentLen] != ':')
- ++currentLen;
- if (currentLen >= trueLen)
- return bdNamErr;
-
- newName[0] = currentLen; // we start with the volume name and first dir...
- parentDirID = 2; // ...which should be at the root directory
- do
- {
- io = DirCreate(0, parentDirID, newName, &createdDirID);
- // Continue while directory exists or can be created.
- if (io == noErr || io == dupFNErr)
- {
- ++currentLen;
- while (currentLen <= trueLen && newName[currentLen] != ':')
- ++currentLen;
- if (currentLen > trueLen)
- break;
- newName[0] = currentLen;
- parentDirID = createdDirID;
- }
- } while (io == noErr || io == dupFNErr);
- newName[0] = reallyTrueLen; // or else!
- return io; // Note even dupFNErr counts as an error at the end.
- }
-
- /* Expect file or dir name, followed by array to place list. Return
- number of files in directory. */
- NODE *do_list(NODE *tree)
- {
- NODE *t1, *t2;
- NODE *ind;
- NODE *res;
- char *name;
- short num_args;
-
- if ((num_args = b_get_two(tree, &t1, &t2)) < 2)
- fatal("list requires at least two arguments");
- name = force_string(t1)->stptr;
- ind = t2;
- if (t2->type == Node_param_list)
- ind = stack_ptr[t2->param_cnt];
- if (ind->type != Node_var && ind->type != Node_var_array)
- fatal("second argument of list is not a variable");
- assoc_clear(ind);
- res = tmp_number((AWKNUM)list_files_or_dirs(name, t1->stlen+1, ind, FALSE));
- free_temp(t1);
- return res;
- }
-
- NODE *do_nested(NODE *tree)
- {
- NODE *t1, *t2;
- NODE *ind;
- NODE *res;
- char *name;
- short num_args;
-
- if ((num_args = b_get_two(tree, &t1, &t2)) < 2)
- fatal("nested requires at least two arguments");
- name = force_string(t1)->stptr;
- ind = t2;
- if (t2->type == Node_param_list)
- ind = stack_ptr[t2->param_cnt];
- if (ind->type != Node_var && ind->type != Node_var_array)
- fatal("second argument of nested is not a variable");
- assoc_clear(ind);
- res = tmp_number((AWKNUM)list_files_or_dirs(name, t1->stlen+1, ind, TRUE));
- free_temp(t1);
- return res;
- }
-
- /* Given full path name, index thru a directory via PBGetCatInfo.
- Return number of files/dirs. Create list of TEXT file names
- or directory names in array "ind", indexed 1..numFiles.
- */
- short list_files_or_dirs(char *theName, short nmlen, NODE *ind, Boolean dirs)
- {
- HVolumeParam vParms;
- CInfoPBPtr cipbr = (CInfoPBPtr)&vParms;
- HFileInfo *fpb = (HFileInfo *)&vParms;
- DirInfo *dpb = (DirInfo *)&vParms;
- WDPBRec theParms;
- char fName[256], volName[32], dirName[256]; /* dirName becomes full name */
- long dirID;
- short index = 1, numFiles = 0, strln, dirLen, i, theVolRef;
- OSErr err;
-
- BlockMove(theName, fName, nmlen);
- CtoPstr(fName);
-
- fpb->ioNamePtr = (StringPtr)fName;
- fpb->ioVRefNum = -32768;
- fpb->ioFDirIndex = 0;
- fpb->ioDirID = 0;
- if (PBGetCatInfo(cipbr, FALSE))
- return(0);
- if (((fpb->ioFlAttrib>>4) & 0x01) != 1) /* a file */
- {
- dirID = fpb->ioFlParID;
- strln = nmlen-1;
- while (strln > 0 && *(theName + strln-1) != ':')
- --strln;
- if (strln <= 0) /* whole name was just a file name */
- {
- strln = 1;
- dirName[0] = ':';
- dirName[1] = '\0';
- }
- else
- {
- BlockMove(theName, dirName, strln);
- dirName[strln] = '\0';
- }
- }
- else /* we were given a directory */
- {
- dirID = dpb->ioDrDirID;
- strln = nmlen-1;
- BlockMove(theName, dirName, strln);
- if (*(theName + strln-1) != ':')
- dirName[strln++] = ':';
- dirName[strln] = '\0';
- }
- dirLen = strln;
-
- /* open working directory */
- i = 0;
- do
- {
- volName[i+1] = dirName[i];
- } while (i < 30 && dirName[i++] != ':');
- volName[0] = i;
- vParms.ioNamePtr = (StringPtr)(volName);
- vParms.ioVRefNum = -32768;
- vParms.ioVolIndex = -1;
- if (PBHGetVInfo((HParmBlkPtr)&vParms, FALSE))
- theParms.ioVRefNum = 0;
- else
- theParms.ioVRefNum = vParms.ioVRefNum;
-
- theParms.ioNamePtr = NULL;
- theParms.ioWDDirID = dirID;
- theParms.ioWDProcID = 'ERIK';
- if (PBOpenWD(&theParms, FALSE)) /* IM IV pg 158 */
- theParms.ioVRefNum = 0;
- theParms.ioNamePtr = NULL;
- theParms.ioWDIndex = 0;
- theParms.ioWDProcID = 0;
- theParms.ioWDVRefNum = 0;
- if (PBGetWDInfo(&theParms,false))
- return(0);
- fpb->ioVRefNum = theParms.ioWDVRefNum;
- fpb->ioNamePtr = (StringPtr)fName;
- do
- {
- fpb->ioFDirIndex = index;
- fpb->ioDirID = dirID;
- if ((err = PBGetCatInfo(cipbr, FALSE)) == noErr)
- {
- if (!dirs && !(fpb->ioFlAttrib & 16)) /* a file */
- {
- /* Is it the right kind of file? */
- if (fpb->ioFlFndrInfo.fdType == 'TEXT')
- {
- ++numFiles;
- strln = fName[0] + dirLen;
- PtoCstr((unsigned char *)fName);
- dirName[dirLen] = '\0';
- strcat(dirName, fName);
- *assoc_lookup(ind, tmp_number((AWKNUM) (numFiles)))
- = make_string(dirName, strln);
- }
- }
- else if (dirs && (fpb->ioFlAttrib & 16)) /* a directory */
- {
- ++numFiles;
- strln = fName[0] + dirLen;
- PtoCstr((unsigned char *)fName);
- dirName[dirLen] = '\0';
- strcat(dirName, fName);
- *assoc_lookup(ind, tmp_number((AWKNUM) (numFiles)))
- = make_string(dirName, strln);
- }
- }
- ++index;
- } while (err == noErr);
- return(numFiles);
- }
-
- NODE *do_rename(NODE *tree)
- {
- NODE *t1, *t2; /* string, string */
- NODE *res;
- char *from, *to;
- short num_args;
-
- if ((num_args = c_get_two(tree, &t1, &t2)) < 2)
- fatal("rename requires at least two arguments");
- from = force_string(t1)->stptr;
- to = force_string(t2)->stptr;
- res = tmp_number((AWKNUM)
- _rename(from, t1->stlen+1, to, t2->stlen+1));
- free_temp(t1);
- free_temp(t2);
- return res;
- }
-
- /* Rename a file. Rename of volumes is not allowed. Location cannot
- be changed with this function. If only a new file name is supplied,
- fill in its full path from the old full pathname. */
- Boolean _rename(Ptr oldNameP, short oldNmLen, Ptr newNameP, short newNmLen)
- {
- char oldName[256], newName[256];
- Ptr tPtr, endPtr;
- Boolean hasColons = FALSE;
-
- if (oldNmLen <= 2 || *(oldNameP + oldNmLen - 2) == ':')
- return(FALSE);
- /* if new name is just a file name, fill in the full path from
- the old name */
- tPtr = newNameP;
- endPtr = newNameP + newNmLen - 1;
- while (tPtr < endPtr)
- {
- if (*tPtr++ == ':')
- {
- hasColons = TRUE;
- break;
- }
- }
- if (!hasColons)
- {
- endPtr = oldNameP + oldNmLen - 2;
- tPtr = oldNameP;
- while (endPtr > tPtr && *(endPtr-1) != ':')
- --endPtr;
- if (endPtr == oldNameP + oldNmLen - 2
- || endPtr < tPtr + 2)
- return(FALSE);
- if (newNmLen + (endPtr - tPtr) > 255)
- return(FALSE);
- BlockMove(newNameP, newName+(endPtr-tPtr), newNmLen);
- BlockMove(tPtr, newName, endPtr-tPtr);
- }
- else
- BlockMove(newNameP, newName, newNmLen);
- BlockMove(oldNameP, oldName, oldNmLen);
- CtoPstr(oldName);
- CtoPstr(newName);
- return(Rename((StringPtr)oldName,0,(StringPtr)newName) == noErr);
- }
-
- NODE *do_remove(NODE *tree)
- {
- NODE *t1, *t2;
-
- if (!tree)
- return tmp_number((AWKNUM)0);
- t1 = force_string(tree_eval(tree->lnode));
- t2 = tmp_number((AWKNUM)_remove(t1->stptr, t1->stlen+1));
- free_temp(t1);
- return t2;
- }
-
- Boolean _remove(char *name, short nmLen)
- {
- FileParam fpb;
- char fName[256];
-
- BlockMove(name, fName, nmLen);
- CtoPstr(fName);
- fpb.ioNamePtr = (StringPtr) fName;
- fpb.ioVRefNum = 0;
- fpb.ioFVersNum = 0;
- return(PBDelete((ParmBlkPtr)&fpb, FALSE) == noErr);
- }
-
- NODE *do_exists(NODE *tree)
- {
- NODE *t1, *t2;
-
- if (!tree)
- return tmp_number((AWKNUM)0);
- t1 = force_string(tree_eval(tree->lnode));
- t2 = tmp_number((AWKNUM)_exists(t1->stptr, t1->stlen+1));
- free_temp(t1);
- return t2;
- }
-
- Boolean _exists(char *name, short nmLen)
- {
- FileParam pb;
- char fName[256];
-
- BlockMove(name, fName, nmLen);
- CtoPstr(fName);
- pb.ioNamePtr = (StringPtr) fName;
- pb.ioVRefNum = 0;
- pb.ioFVersNum = 0;
- pb.ioFDirIndex = 0;
- return(PBGetFInfoSync((ParmBlkPtr)&pb) == noErr);
- }
-
- NODE *do_fdate(NODE *tree)
- {
- NODE *t1, *r;
-
- if (!tree)
- return Nnull_string;
- t1 = force_string(tree_eval(tree->lnode));
- r = _fdate(t1->stptr, t1->stlen+1);
- free_temp(t1);
- return r;
- }
-
- NODE *_fdate(char *name, short nmLen)
- {
- FileParam pb;
- DateTimeRec dtr;
- char fName[256], when[32];
- NODE *r;
-
- BlockMove(name, fName, nmLen);
- CtoPstr(fName);
- pb.ioNamePtr = (StringPtr) fName;
- pb.ioVRefNum = 0;
- pb.ioFVersNum = 0;
- pb.ioFDirIndex = 0;
- if (PBGetFInfoSync((ParmBlkPtr)&pb) != noErr)
- return Nnull_string;
- Secs2Date(pb.ioFlMdDat, &dtr);
- sprintf(when, "%.4d:%.2d:%.2d:%.2d:%.2d:%.2d",
- dtr.year, dtr.month, dtr.day,
- dtr.hour, dtr.minute, dtr.second);
- r = tmp_string(when, 19);
- return r;
- }
-
- NODE *do_fsize(NODE *tree)
- {
- NODE *t1, *t2;
-
- if (!tree)
- return tmp_number((AWKNUM)0);
- t1 = force_string(tree_eval(tree->lnode));
- t2 = tmp_number((AWKNUM)_fsize(t1->stptr, t1->stlen+1));
- free_temp(t1);
- return t2;
- }
-
- long _fsize(char *name, short nmLen)
- {
- FileParam pb;
- char fName[256];
-
- BlockMove(name, fName, nmLen);
- CtoPstr(fName);
- pb.ioNamePtr = (StringPtr) fName;
- pb.ioVRefNum = 0;
- pb.ioFVersNum = 0;
- pb.ioFDirIndex = 0;
- if (PBGetFInfoSync((ParmBlkPtr)&pb) != noErr)
- return(0L);
- return(pb.ioFlLgLen);
- }
-
- NODE *do_getclip(NODE *tree)
- {
- NODE *t1;
- long maxBytes;
-
- if (!tree)
- maxBytes = 0;
- else
- {
- t1 = tree_eval(tree->lnode);
- maxBytes = (long) force_number(t1);
- free_temp(t1);
- }
- return _getclip(maxBytes);
- }
-
- NODE *_getclip(long maxBytes)
- {
- NODE *t1;
- Handle hText;
- long clipSize;
- extern Handle GetTheClip(void);
-
- hText = GetTheClip();
- if (!hText)
- return Nnull_string;
- if (maxBytes < 0)
- maxBytes = 0;
- clipSize = GetHandleSize(hText);
- if (!clipSize)
- return Nnull_string;
- if (maxBytes && clipSize > maxBytes)
- clipSize = maxBytes;
- // Revision, keep it short.
- if (clipSize > 32766L)
- clipSize = 32766L;
- HLock(hText);
- t1 = tmp_string(*hText, clipSize);
- HUnlock(hText);
- return t1;
- }
-
- NODE *do_putclip(NODE *tree)
- {
- NODE *t1, *t2;
-
- if (!tree)
- {
- char c = 0;
- t2 = tmp_number((AWKNUM)_putclip(&c));
- }
- else
- {
- t1 = force_string(tree_eval(tree->lnode));
- t2 = tmp_number((AWKNUM)_putclip(t1->stptr));
- free_temp(t1);
- }
- return t2;
- }
-
- short _putclip(char *str)
- {
- extern short PutTheClip(char *newClipStr);
-
- return PutTheClip(str);
- }
-
- NODE *do_beep(NODE *tree)
- {
- NODE *t1;
- short duration, shortlevel;
-
- if (!tree)
- duration = 2;
- else
- {
- t1 = tree_eval(tree->lnode);
- duration = (short) force_number(t1);
- free_temp(t1);
- }
- if (duration < 0)
- duration = 0;
- if (!duration)
- {
- long finalTick;
- FlashMenuBar(0);
- Delay(2, &finalTick);
- FlashMenuBar(0);
- }
- else
- SysBeep(duration);
- return Nnull_string;
- }
-